import uuid
import os
import requests
import json

try:
    from urllib.parse import urlencode
except ImportError:
    from urllib import urlencode
import re

from . import __version__
import base64
import datetime
import logging
from .exception import PaymentException

log = logging.getLogger('payment')


def join_url(url, *paths):
    """
    Joins individual URL strings together, and returns a single string.

    Usage::

        >>> util.join_url("example.com", "index.html")
        'example.com/index.html'
    """
    for path in paths:
        url = re.sub(r'/?$', re.sub(r'^/?', '/', path), url)
    return url


def join_url_params(url, params):
    """Constructs percent-encoded query string from given parms dictionary
     and appends to given url

    Usage::

        >>> util.join_url_params("example.com/index.html", {"page-id": 2, "Company": "Pay Pal"})
        example.com/index.html?page-id=2&Company=Pay+Pal
    """
    return url + "?" + urlencode(params)


class Api(object):
    user_agent = "Payment-Python-SDK %s  " % __version__

    def __init__(self, args={}, **kwargs):
        self.client_id = kwargs['client_id']
        self.endpoint = kwargs.pop('endpoint', None)
        self.client_secret = kwargs['client_secret']
        self.options = args
        self.token_hash = None
        self.token_request_at = None

    def post(self, url, data={}, headers={}):
        url = join_url(self.endpoint, url)
        return self._request(url, 'POST', data=data, headers=headers)

    def headers(self):
        token_hash = self._get_token()

        return {
            "Authorization": ("%s %s" % (token_hash['token_type'], token_hash['access_token'])),
            "Accept": "application/json",
            "Content-Type": "application/json",
            "User-Agent": self.user_agent,
            'Request-id': str(uuid.uuid4())
        }

    def get(self, url, params, headers=None, **kwargs):
        url = join_url(self.endpoint, url)
        if params:
            url = join_url_params(url, params)

        return self._request(url, 'GET', None, headers, **kwargs)

    def _request(self, url, method, data, headers, **kwargs):

        http_headers = self.headers()
        if headers:
            http_headers.update(headers)
        try:
            return self._http_call(url=url, method=method, data=json.dumps(data), headers=http_headers, **kwargs)
        except PaymentException as pe:
            if pe.code == 401:
                self.token_hash = None
                return self._request(url, method, data, headers, **kwargs)
            else:
                raise pe


    def _handle_response(self, response):

        status = response.status_code

        if 200 <= status <= 299:
            return response.json()

        elif status == 400:
            return {'error': response.content}
        elif status == 500:
            return {'error': response.content}
        else:
            from .exception import PaymentException
            raise PaymentException(status, response.content)

    def _get_token(self):
        self._validate_token()

        if self.token_hash:
            return self.token_hash
        else:
            path = '/oauth/token'
            data = {
                'grant_type': 'client_credentials'
            }
            headers = {
                "Authorization": ("Basic %s" % self.basic_auth()),
                "Content-Type": "application/x-www-form-urlencoded",
                "Accept": "application/json", "User-Agent": self.user_agent
            }
            return self._http_call(join_url(self.endpoint, path), 'POST', data=data, headers=headers)

    def basic_auth(self):
        credentials = "%s:%s" % (self.client_id, self.client_secret)
        return base64.b64encode(credentials.encode('utf-8')).decode('utf-8').replace("\n", "")

    def _http_call(self, url, method, data, headers, **kwargs):
        log.info('Request[%s]: %s' % (method, url))

        start_time = datetime.datetime.now()
        log.debug('Request: \nHeaders: %s\nBody: %s' % (
            str(headers), str(data)))
        response = requests.request(method=method, data=data, url=url, headers=headers, **kwargs)

        duration = datetime.datetime.now() - start_time
        log.info('Response[%d]: %s, Duration: %s.%ss.' % (
            response.status_code, response.reason, duration.seconds, duration.microseconds))

        return self._handle_response(response)

    def _validate_token(self):
        if self.token_request_at and self.token_hash and self.token_hash.get("expires") is not None:
            delta = datetime.datetime.now() - self.token_request_at
            duration = (
                           delta.microseconds + (delta.seconds + delta.days * 24 * 3600) * 10 ** 6) / 10 ** 6
            if duration > self.token_hash.get("expires"):
                self.token_hash = None


__api__ = None


def default():
    """Returns default api object and if not present creates a new one
    By default points to developer sandbox
    """
    global __api__
    if __api__ is None:
        try:
            client_id = os.environ["PAYMENT_CLIENT_ID"]
            client_secret = os.environ["PAYMENT_CLIENT_SECRET"]
            endpoint = os.environ['PAYMENT_ENDPOINT']
        except KeyError:
            # raise exceptions.MissingConfig("Required PAYPAL_CLIENT_ID and PAYPAL_CLIENT_SECRET. \
            #     Refer https://github.com/paypal/rest-api-sdk-python#configuration")
            pass

        __api__ = Api(client_id=client_id, client_secret=client_secret, endpoint=endpoint)
    return __api__


def set_config(options=None, **config):
    """Create new default api object with given configuration
    """
    global __api__
    __api__ = Api(options or {}, **config)
    return __api__


configure = set_config
